package file.util;

import file.FileInfo;
import file.config.FsConfig;
import file.config.MyConfigProperties;
import file.config.JvmConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.servlet.ServletOutputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * @author hh
 * @since 2024/12/7
 */
public class FileUtil {

    private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
    public static final String MY_FILE_PATH = "MyFileSystem";
    public static final String SEP = "_";
    private static final String defaultPath;
    public static boolean isWindows;

    private FileUtil() {
    }

    public static String getDefaultPath() {
        return defaultPath;
    }

    static {
        String osName = System.getProperty("os.name");
        if (osName.contains("Windows")) {
            isWindows = true;
            defaultPath = System.getProperty("user.home") + "\\" + MY_FILE_PATH + "\\";
        } else {
            isWindows = false;
            defaultPath = System.getProperty("user.home") + "/" + MY_FILE_PATH + "/";
        }
    }

    public static String readTextFile(String fileName, boolean isEncrypt) throws Exception {
        MyConfigProperties config = JvmConfig.getConfig();
        String realPath = getRealPath(config.getFilePath());
        if (isEncrypt) {
            fileName = convertSeparator(AESUtil.encrypt(fileName));
        }
        File file = new File(realPath + fileName);
        if (!file.exists()) {
            throw new Exception("The file cannot be found on the server");
        }
        return readTextFile(file, isEncrypt);
    }

    public static String readTextFile(File file, boolean isEncrypt) throws Exception {
        StringBuilder sb = new StringBuilder();
        List<String> textFileList = readTextFileList(file);
        textFileList.forEach(sb::append);
        if (isEncrypt) {
            return AESUtil.decrypt(sb.toString());
        }
        return sb.toString();
    }

    public static List<String> readTextFileList(File file) throws Exception {
        List<String> textLines = new ArrayList<>();
        try (FileReader fr = new FileReader(file);
             BufferedReader br = new BufferedReader(fr)){
            String line;
            while ((line = br.readLine()) != null) {
                textLines.add(line);
            }
        }
        return textLines;
    }

    public static void saveFile(InputStream inputStream, String fileName) throws Exception {
        MyConfigProperties config = JvmConfig.getConfig();
        String configPath = config.getFilePath();
        boolean isEncrypt = config.isEncrypt();

        String realPath = FileUtil.getRealPath(configPath);
        File directory = new File(realPath);
        if (!directory.exists()) {
            boolean mkdir = directory.mkdir();
            if (mkdir) {
                log.info("Directory: [{}] create successful!", realPath);
            }
        }
        String fileNameEncrypt = isEncrypt ? convertSeparator(AESUtil.encrypt(fileName)) : fileName;

        InputStream is = null;
        BufferedInputStream bis = null;
        try (FileOutputStream fos = new FileOutputStream(realPath + fileNameEncrypt);
             BufferedOutputStream bos = new BufferedOutputStream(fos)){
            if (isEncrypt) {
                Cipher cipher = AESUtil.getCipher(Cipher.ENCRYPT_MODE);
                is = new CipherInputStream(inputStream, cipher);
            } else {
                is = inputStream;
            }
            bis = new BufferedInputStream(is);

            int read;
            byte[] buffer = new byte[1024];
            while ((read = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, read);
            }
            bos.flush();
        } catch (IOException e) {
            throw new IOException("Save File error");
        } finally {
            if (bis != null) {
                bis.close();
            }
            if (is != null) {
                is.close();
            }
        }
    }

    public static void saveFile(String content, String savePath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(savePath);
             BufferedOutputStream bos = new BufferedOutputStream(fos)){
            bos.write(content.getBytes(StandardCharsets.UTF_8));
            bos.flush();
        } catch (IOException e) {
            throw new IOException("Save file error");
        }
    }

    public static void writeOutPutStream(ServletOutputStream outputStream, String fileName) throws Exception {
        MyConfigProperties config = JvmConfig.getConfig();
        String configPath = config.getFilePath();
        boolean isEncrypt = config.isEncrypt();

        String realPath = getRealPath(configPath);
        File file = new File(realPath + fileName);
        if (!file.exists()) {
            log.warn("The file cannot be found on the server");
            return;
        }
        OutputStream os = null;
        BufferedOutputStream bos = null;
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis)){
            if (isEncrypt) {
                Cipher cipher = AESUtil.getCipher(Cipher.DECRYPT_MODE);
                os = new CipherOutputStream(outputStream, cipher);
            } else {
                os = outputStream;
            }
            bos = new BufferedOutputStream(os);

            int read;
            byte[] buffer = new byte[1024];
            while ((read = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, read);
            }
            bos.flush();
            os.flush();
        } catch (IOException e) {
            throw new IOException("Write OutPutStream error");
        } finally {
            if (bos != null) {
                bos.close();
            }
            if (os != null) {
                os.close();
            }
        }
    }

    public static FileInfo getFileInfoByFileName(String fileName) throws Exception {
        String name = getSameFileName(fileName);
        if (name == null) {
            return null;
        }
        return FileInfoUtil.getFileInfo(name.split(SEP));
    }

    public static void deleteFileByFileName(String fileName) throws Exception {
        MyConfigProperties config = JvmConfig.getConfig();
        String configPath = config.getFilePath();
        boolean isEncrypt = config.isEncrypt();

        String realPath = FileUtil.getRealPath(configPath);
        String needDeleteFileName = getSameFileName(fileName);
        if (needDeleteFileName == null) {
            return;
        }
        doDelete(isEncrypt, needDeleteFileName, realPath);
    }

    private static boolean doDelete(boolean isEncrypt, String needDeleteFileName, String realPath) throws Exception {
        String realFileName = isEncrypt ? convertSeparator(AESUtil.encrypt(needDeleteFileName)) : needDeleteFileName;
        File file = new File(realPath + realFileName);
        if (!file.exists()) {
            log.warn("File [{}] is not exists", realPath + realFileName);
            return false;
        }
        return file.delete();
    }

    public static boolean deleteFileById(String id) throws Exception {
        MyConfigProperties config = JvmConfig.getConfig();
        String configPath = config.getFilePath();
        boolean isEncrypt = config.isEncrypt();

        String realPath = FileUtil.getRealPath(configPath);
        String needDeleteFileName = null;
        List<String> ls = ls();
        for (String resource : ls) {
            if (resource.startsWith(id + SEP)) {
                needDeleteFileName = resource;
            }
        }
        if (needDeleteFileName == null) {
            throw new Exception("文件[" + id + "]不存在！");
        }
        return doDelete(isEncrypt, needDeleteFileName, realPath);
    }

    public static List<String> ls() throws Exception {
        MyConfigProperties config = JvmConfig.getConfig();
        String configPath = config.getFilePath();
        boolean isEncrypt = config.isEncrypt();

        File directory = new File(getRealPath(configPath));
        String[] encryptFileNames = directory.list();
        if (encryptFileNames == null) {
            return Collections.emptyList();
        }
        List<String> decryptFileNameList = new ArrayList<>();
        for (String encryptFileName : encryptFileNames) {
            if (FsConfig.CONFIG_FILE_NAME.equals(encryptFileName) || encryptFileName.startsWith(".")) {
                continue;
            }
            decryptFileNameList.add(isEncrypt ? AESUtil.decrypt(deConvertSeparator(encryptFileName)) : encryptFileName);
        }
        return decryptFileNameList;
    }

    private static String getSameFileName(String fileName) throws Exception {
        List<String> ls = ls();
        for (String l : ls) {
            if (!StringUtils.hasText(l)) {
                continue;
            }
            String[] split = l.split(SEP);
            if (Objects.equals(split[1], fileName)) {
                return l;
            }
        }
        return null;
    }

    public static String getRealPath(String configPath) {
        String realPath = defaultPath;
        if (StringUtils.hasText(configPath)) {
            if (configPath.endsWith("/") || configPath.endsWith("\\")) {
                realPath = configPath;
            } else {
                String osName = System.getProperty("os.name");
                if (osName.contains("Windows")) {
                    realPath = configPath + "/";
                } else {
                    realPath = configPath + "\\";
                }
            }
        }
        return realPath;
    }

    private static String convertSeparator(String encrypt) {
        if (encrypt.contains("/")) {
            encrypt = encrypt.replaceAll("/", "___");
        }
        if (encrypt.contains("\\")) {
            encrypt = encrypt.replaceAll("\\\\", "~~~");
        }
        return encrypt;
    }

    private static String deConvertSeparator(String encrypt) {
        if (encrypt.contains("___")) {
            encrypt = encrypt.replaceAll("___", "/");
        }
        if (encrypt.contains("~~~")) {
            encrypt = encrypt.replaceAll("~~~", "\\");
        }
        return encrypt;
    }

}
